#pragma once

#include <bit>
#include <concepts>
#include <cstdint>
#include <type_traits>

#include <utils/Concepts.h>
#include <utils/bit_utils/CudaWrapper.h>
#include <utils/config.h>


namespace solar::bit
{
    // value = conditional ? value + add : value
    template <Number T1, std::integral T2>
    constexpr void conditionalAdd(bool condition, T1& value, const T2 add)
    {
        value += -std::make_signed_t<T2>(condition) & add;
    }

    // value = conditional ? value + add : value
    template <Number T1, std::integral T2>
    constexpr auto conditionalAdd2(bool condition, T1 value, const T2 add) -> T1
    {
        return value + (-std::make_signed_t<T2>(condition) & add);
    }

    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ void conditionalAdd(bool condition, T1& value, const float add)
    {
        auto& tmp_add = reinterpret_cast<const int&>(add);
        auto tmp = -int(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const float&>(tmp);
        value += tmp_result;
    }

    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ void conditionalAdd(bool condition, T1& value, const double add)
    {
        auto& tmp_add = reinterpret_cast<const std::int64_t&>(add);
        auto tmp = -std::int64_t(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const double&>(tmp);
        value += tmp_result;
    }

    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ void conditionalAdd(bool condition, T1& value, const long double add)
        requires(sizeof(long double) == 8)
    {
        auto& tmp_add = reinterpret_cast<const std::int64_t&>(add);
        auto tmp = -std::int64_t(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const long double&>(tmp);
        value += tmp_result;
    }

    // TODO: Support conditionalAdd<long double> in 80-bit mode

#if !(defined(_MSC_VER) && !defined(__clang__))
    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ void conditionalAdd(bool condition, T1& value, const long double add)
        requires(sizeof(long double) == 16)
    {
        auto& tmp_add = reinterpret_cast<const __int128&>(add);
        auto tmp = -__int128(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const long double&>(tmp);
        value += tmp_result;
    }
#endif

    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ auto conditionalAdd2(bool condition, T1 value, const float add) -> T1
    {
        auto& tmp_add = reinterpret_cast<const int&>(add);
        auto tmp = -int(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const float&>(tmp);
        return value + tmp_result;
    }

    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ auto conditionalAdd2(bool condition, T1 value, const double add)
    {
        auto& tmp_add = reinterpret_cast<const std::int64_t&>(add);
        auto tmp = -std::int64_t(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const double&>(tmp);
        return value + tmp_result;
    }

    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ auto conditionalAdd2(bool condition, T1 value,
                                                    const long double add) -> T1
        requires(sizeof(long double) == 8)
    {
        auto& tmp_add = reinterpret_cast<const std::int64_t&>(add);
        auto tmp = -std::int64_t(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const long double&>(tmp);
        return value + tmp_result;
    }

    // TODO: Support conditionalAdd<long double> in 80-bit mode

#if !(defined(_MSC_VER) && !defined(__clang__))
    // value = conditional ? value + add : value
    template <Number T1>
    inline __host__ __device__ auto conditionalAdd2(bool condition, T1 value,
                                                    const long double add) -> T1
        requires(sizeof(long double) == 16)
    {
        auto& tmp_add = reinterpret_cast<const __int128&>(add);
        auto tmp = -__int128(condition) & tmp_add;
        auto& tmp_result = reinterpret_cast<const long double&>(tmp);
        return value + tmp_result;
    }
#endif

    namespace const_expr
    {
        // value = conditional ? value + add : value
        template <Number T1>
        constexpr void conditionalAdd(bool condition, T1& value, const float add)
        {
            value += std::bit_cast<float>(-int(condition) & std::bit_cast<int>(add));
        }

        // value = conditional ? value + add : value
        template <Number T1>
        constexpr void conditionalAdd(bool condition, T1& value, const double add)
        {
            value +=
                std::bit_cast<double>(-std::int64_t(condition) & std::bit_cast<std::int64_t>(add));
        }

        // value = conditional ? value + add : value
        template <Number T1>
        constexpr void conditionalAdd(bool condition, T1& value, const long double add)
            requires(sizeof(long double) == 8)
        {
            value +=
                std::bit_cast<double>(-std::int64_t(condition) & std::bit_cast<std::int64_t>(add));
        }

        // TODO: Support conditionalAdd<long double> in 80-bit mode

#if !(defined(_MSC_VER) && !defined(__clang__))
        // value = conditional ? value + add : value
        template <Number T1>
        constexpr void conditionalAdd(bool condition, T1& value, const long double add)
            requires(sizeof(long double) == 16)
        {
            value +=
                std::bit_cast<long double>(-__int128(condition) & std::bit_cast<__int128>(add));
        }
#endif

        // value = conditional ? value + add : value
        template <Number T1>
        constexpr auto conditionalAdd2(bool condition, T1 value, const float add) -> T1
        {
            return value + (std::bit_cast<float>(-int(condition) & std::bit_cast<int>(add)));
        }

        // value = conditional ? value + add : value
        template <Number T1>
        constexpr auto conditionalAdd2(bool condition, T1 value, const double add)
        {
            return value + (std::bit_cast<double>(-std::int64_t(condition) &
                                                  std::bit_cast<std::int64_t>(add)));
        }

        // value = conditional ? value + add : value
        template <Number T1>
        constexpr auto conditionalAdd2(bool condition, T1 value, const long double add) -> T1
            requires(sizeof(long double) == 8)
        {
            return value + (std::bit_cast<double>(-std::int64_t(condition) &
                                                  std::bit_cast<std::int64_t>(add)));
        }

        // TODO: Support conditionalAdd<long double> in 80-bit mode

#if !(defined(_MSC_VER) && !defined(__clang__))
        // value = conditional ? value + add : value
        template <Number T1>
        constexpr auto conditionalAdd2(bool condition, T1 value, const long double add) -> T1
            requires(sizeof(long double) == 16)
        {
            return value + (std::bit_cast<long double>(-__int128(condition) &
                                                       std::bit_cast<__int128>(add)));
        }
#endif
    } // namespace const_expr

} // namespace solar::bit
